import contextlib
import datetime
from typing import Union, Optional

import allure

import settings
from common.core.meta import Xpath, ApiAlias, AliasError
from common.core.globals import p, PageRouterManager
from common.core.page import BasePage
from common.core.page.mixins import ElementsLookUpMixin


class page_component:
    def __init__(self, attr_name: str, alias: str, belong_to: Optional[str] = None):
        self._attr_name = attr_name
        self._alias = alias
        page_klass = PageRouterManager.look_up(belong_to) if belong_to else BasePage
        if hasattr(page_klass, attr_name):
            raise AttributeError(f"{page_klass.__name__} 中已经有 {attr_name} 的别名")
        if hasattr(page_klass, alias):
            raise AliasError(f"{page_klass.__name__} 已经存在别名为 {alias} 的属性")
        self._page_klass = page_klass

    def __call__(self, component: type(ApiAlias)):
        c = component(self._alias)
        setattr(self._page_klass, self._alias, c)
        setattr(self._page_klass, self._attr_name, c)


# 管理后台通用输入框
@page_component(attr_name="search_ipt", alias="搜索框")
class SearchField(ApiAlias):
    ipt_xpath = "//div/label[contains(@title, '%s')]/../../..//input"

    def __call__(self, name: str):
        self.ipt_xpath = self.ipt_xpath % name
        self.name = name
        return self

    def send_keys(self, val):
        ipt_locator = Xpath(self.ipt_xpath, name="%s输入框" % self.name)
        p.find(ipt_locator).send_keys(val)


# VUE 下拉选择
@page_component(attr_name="selector", alias="下拉框")
class VueSelector(ApiAlias):
    ipt_xpath = "//div/label[contains(@title, '%s')]/../../..//*[@role='combobox']"  # 主框
    option_xpath = "//li[@role='option' and contains(text(), '%s')] "  # 选项

    def __call__(self, name: str):
        self.ipt_xpath = self.ipt_xpath % name
        self.name = name
        return self

    # 点击一下输入框用于展开
    @contextlib.contextmanager
    def selector_fixture(self):
        ipt_locator = Xpath(self.ipt_xpath, name="%s下拉框" % self.name, )
        p.find(ipt_locator).click()
        yield

    def select_option(self, text: str):
        """
        :param text: 选择的文本内容，注意需要 F12 复制选择，防止中间存在空格之类的影响定位
        """
        with self.selector_fixture():
            locator = Xpath(self.option_xpath % text, name="%s 选项" % text, )
            p.find(locator).click()

    @property
    def chosen(self):
        """
        获取当前选择项
        """
        text_locator = Xpath(self.ipt_xpath + "//div[text()]", name="%s选中内容" % self.name)
        return p.find(text_locator).text()


# 时间控件
@page_component(attr_name="time_range", alias="时间控件")
class TimeRange(ApiAlias, ElementsLookUpMixin):
    elements = {
        "开始时间": "//div/label[contains(@title, '%s')]/../../..//div[@class='ant-col ant-col-11'][1]//input",
        "结束时间": "//div/label[contains(@title, '%s')]/../../..//div[@class='ant-col ant-col-11'][2]//input",

        "上一年": "//*[@class= 'ant-calendar-header']/div[@style]//*[contains(@title, '上一年')]",
        "下一年": "//*[@class= 'ant-calendar-header']/div[@style]//*[contains(@title, '下一年')]",
        "当前年": "//*[@class= 'ant-calendar-header']/div[@style]//*[contains(@title, '年份')]",

        "上一月": "//*[@class= 'ant-calendar-header']/div[@style]//*[contains(@title, '上个月')]",
        "下一月": "//*[@class= 'ant-calendar-header']/div[@style]//*[contains(@title, '下个月')]",
        "当前月": "//*[@class= 'ant-calendar-header']/div[@style]//*[contains(@title, '月份')]",

        "天分": "//*[@class= 'ant-calendar-body']//*[@class='ant-calendar-cell' or"
                " @class='ant-calendar-cell ant-calendar-last-day-of-month']//*[text()='%s']",
        "当前天": "//*[@class= 'ant-calendar-body']//*[contains(@class, 'selected-day')]/div",
    }

    def __call__(self, name: str):
        self.name = name
        return self

    @staticmethod
    def _parse_time(time_str: str):
        try:
            return datetime.datetime.strptime(time_str, settings.TIME_FMT)
        except Exception:
            raise TypeError("%s 不符合 %s 的格式，解析失败" % (time_str, settings.TIME_FMT)) from None

    # 面板上显示的年份
    @property
    def panel_year(self):
        locator = self.get_locator("当前年")
        return int(p.find(locator).text().strip("年"))

    # 设置面板年份
    @panel_year.setter
    def panel_year(self, year: int):
        with allure.step("选择 %s 年" % year):
            panel_year = self.panel_year
            if year >= panel_year:
                locator = self.get_locator("下一年")
            else:
                locator = self.get_locator("上一年")
            if (sub := abs(year - panel_year)) > 0:
                for _ in range(sub):
                    p.find(locator).click()

    # 面板上显示的月份
    @property
    def panel_month(self):
        locator = self.get_locator("当前月")
        return int(p.find(locator).text().strip("月"))

    # 设置面板月份
    @panel_month.setter
    def panel_month(self, mouth: int):
        with allure.step("选择 %s 月" % mouth):
            panel_month = self.panel_month
            if mouth >= panel_month:
                locator = self.get_locator("下一月")
            else:
                locator = self.get_locator("上一月")

            if (sub := abs(mouth - panel_month)) > 0:
                for _ in range(sub):
                    p.find(locator).click()

    # 当前面板天数
    @property
    def panel_day(self):
        locator = self.get_locator("当前天")
        return int(p.find(locator).text())

    # 设置面板天数
    @panel_day.setter
    def panel_day(self, day: int):
        with allure.step("选择 %s 天" % day):
            if self.panel_day != day:
                locator = self.get_locator("天分")
                p.find(locator).click()

    def _choose_time(self, t: datetime.datetime):
        self.panel_year = t.year
        self.panel_month = t.month
        self.panel_day = t.day

    def choose_time(self, start_time: Union[datetime.datetime, str], end_time: Union[datetime.datetime, str]):
        start = start_time if isinstance(start_time, datetime.datetime) else self._parse_time(start_time)
        end = end_time if isinstance(end_time, datetime.datetime) else self._parse_time(end_time)
        p.find(self.fmt_locator("开始时间", self.name)).click()
        self._choose_time(start)

        p.find(self.fmt_locator("结束时间", self.name)).click()
        self._choose_time(end)


# 处理页码
class Pagination(ApiAlias):
    ...


# 弹出框统一处理
class EnsueAlert(ApiAlias):
    ...
